<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>Android图片压缩——Luban鲁班压缩 | 海晨忆的博客</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  
  
  
  <meta name="description" content="&amp;emsp;&amp;emsp;Android让人头疼的OOM，造成OOM的原因之一就是图片，现在的手机像素越来越高，随便一张图片都是好几M，甚至几十M，这样的照片加载到app，可想而知，随便加载几张图片，手机内存就不够用了，自然而然就造成了OOM，所以，Android的图片压缩异常重要。这里，我推荐一款开源框架——Luban">
<meta property="og:type" content="article">
<meta property="og:title" content="Android图片压缩——Luban鲁班压缩">
<meta property="og:url" content="https://haichenyi.com/2018/03/02/Android图片压缩——Luban鲁班压缩/index.html">
<meta property="og:site_name" content="海晨忆的博客">
<meta property="og:description" content="&amp;emsp;&amp;emsp;Android让人头疼的OOM，造成OOM的原因之一就是图片，现在的手机像素越来越高，随便一张图片都是好几M，甚至几十M，这样的照片加载到app，可想而知，随便加载几张图片，手机内存就不够用了，自然而然就造成了OOM，所以，Android的图片压缩异常重要。这里，我推荐一款开源框架——Luban">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2018-03-02T12:16:22.942Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Android图片压缩——Luban鲁班压缩">
<meta name="twitter:description" content="&amp;emsp;&amp;emsp;Android让人头疼的OOM，造成OOM的原因之一就是图片，现在的手机像素越来越高，随便一张图片都是好几M，甚至几十M，这样的照片加载到app，可想而知，随便加载几张图片，手机内存就不够用了，自然而然就造成了OOM，所以，Android的图片压缩异常重要。这里，我推荐一款开源框架——Luban">
  
    <link rel="alternate" href="/atom.xml" title="海晨忆的博客" type="application/atom+xml">
  
  
    <link rel="icon" href="/uploads/artistic_image/head.jpg">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
  <link rel="stylesheet" href="/css/highlight.css">
  <script>
  let antiquityStorage = window.sessionStorage.getItem('antiquitySessionStorage');
  if (antiquityStorage == '' || antiquityStorage == null) {
    var antiquityLoader = '<div id="loaderbox"><div class="loader"><div class="load-roll"><div class="load-top"></div><div class="load-right"></div><div class="load-bottom"></div></div></div></div>';
    document.write(antiquityLoader);
    document.body.style.overflow = 'hidden'
  }
  </script>
</head>

<body>
  <div id="fullpage" class="mobile-nav-right">
    
      <div id="wrapper" style="background-image: url(/uploads/artistic_image/bg.jpg)" title="背景图片来自网络">
    
    
      <header id="header">
  <div id="nav-toggle" class="nav-toggle"></div>
  <div class="head-box global-width">
    <nav class="nav-box nav-right">
      
        <a class="nav-item" href="/" title
        
        >首页</a>
      
        <a class="nav-item" href="/archives" title
        
        >归档</a>
      
        <a class="nav-item" href="/about" title
        
        >简历</a>
      
    </nav>
  </div>
</header>
      <div id="middlecontent" title class="global-width sidebar-left">
        <section id="main"><article id="post-Android图片压缩——Luban鲁班压缩" class="article global-container article-type-post" itemscope itemprop="blogPost">
  
    <header class="article-header">
      
  
    <h1 class="article-title" itemprop="name">
      Android图片压缩——Luban鲁班压缩
    </h1>
  

    </header>
  
  <div class="article-meta">
    <a href="/2018/03/02/Android图片压缩——Luban鲁班压缩/" class="article-date">
  <time datetime="2018-03-02T12:14:04.000Z" itemprop="datePublished">2018-03-02</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Android-源码解析/">Android -源码解析</a>
  </div>

    
  </div>
  
    <span id="busuanzi_container_page_pv">
      本文总阅读量<span id="busuanzi_value_page_pv"></span>次
    </span>
  

  <div class="article-inner">
    
    <div class="article-content article-content-cloud doorframe mac" itemprop="articleBody">
      
        <p>&emsp;&emsp;Android让人头疼的OOM，造成OOM的原因之一就是图片，现在的手机像素越来越高，随便一张图片都是好几M，甚至几十M，这样的照片加载到app，可想而知，随便加载几张图片，手机内存就不够用了，自然而然就造成了OOM，所以，Android的图片压缩异常重要。这里，我推荐一款开源框架——<a href="https://github.com/Curzibn/Luban" target="_blank" rel="noopener">Luban</a></p>
<a id="more"></a>
<h2 id="效果与对比"><a href="#效果与对比" class="headerlink" title="效果与对比"></a>效果与对比</h2><p>&emsp;&emsp;这里就不放效果图了，我拷贝了鲁班github上面的介绍——Android图片压缩工具，仿微信朋友圈压缩策略，因为是逆向推算，效果还没法跟微信一模一样，但是已经很接近微信朋友圈压缩后的效果，具体看以下对比！</p>
<table>
<thead>
<tr>
<th style="text-align:center">内容</th>
<th style="text-align:center">原图</th>
<th style="text-align:center">Luban</th>
<th style="text-align:center">Wechat</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">截屏 720P</td>
<td style="text-align:center">720*1280,390k</td>
<td style="text-align:center">720*1280,87k</td>
<td style="text-align:center">720*1280,56k</td>
</tr>
<tr>
<td style="text-align:center">截屏 1080P</td>
<td style="text-align:center">1080*1920,2.21M</td>
<td style="text-align:center">1080*1920,104k</td>
<td style="text-align:center">1080*1920,112k</td>
</tr>
<tr>
<td style="text-align:center">拍照 13M(4:3)</td>
<td style="text-align:center">3096*4128,3.12M</td>
<td style="text-align:center">1548*2064,141k</td>
<td style="text-align:center">1548*2064,147k</td>
</tr>
<tr>
<td style="text-align:center">拍照 9.6M(16:9)</td>
<td style="text-align:center">4128*2322,4.64M</td>
<td style="text-align:center">1032*581,97k</td>
<td style="text-align:center">1032*581,74k</td>
</tr>
<tr>
<td style="text-align:center">滚动截屏</td>
<td style="text-align:center">1080*6433,1.56M</td>
<td style="text-align:center">1080*6433,351k</td>
<td style="text-align:center">1080*6433,482k</td>
</tr>
</tbody>
</table>
<p>从这里就能看出，效果还是非常不错的</p>
<h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">implementation &apos;top.zibin:Luban:1.1.3&apos;</span><br></pre></td></tr></table></figure></div>
<h2 id="调用方式"><a href="#调用方式" class="headerlink" title="调用方式"></a>调用方式</h2><h3 id="异步调用"><a href="#异步调用" class="headerlink" title="异步调用"></a>异步调用</h3><p>&emsp;&emsp;Luban内部采用IO线程进行图片压缩，外部调用只需设置好结果监听即可：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Luban.with(this)</span><br><span class="line">        .load(photos)                                   // 传人要压缩的图片列表</span><br><span class="line">        .ignoreBy(100)                                  // 忽略不压缩图片的大小</span><br><span class="line">        .setTargetDir(getPath())                        // 设置压缩后文件存储位置</span><br><span class="line">        .setCompressListener(new OnCompressListener() &#123; //设置回调</span><br><span class="line">          @Override</span><br><span class="line">          public void onStart() &#123;</span><br><span class="line">            // TODO 压缩开始前调用，可以在方法内启动 loading UI</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          @Override</span><br><span class="line">          public void onSuccess(File file) &#123;</span><br><span class="line">            // TODO 压缩成功后调用，返回压缩后的图片文件</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          @Override</span><br><span class="line">          public void onError(Throwable e) &#123;</span><br><span class="line">            // TODO 当压缩过程出现问题时调用</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;).launch();    //启动压缩</span><br></pre></td></tr></table></figure></div>
<h3 id="同步调用"><a href="#同步调用" class="headerlink" title="同步调用"></a>同步调用</h3><p>&emsp;&emsp;同步方法请尽量避免在主线程调用以免阻塞主线程，下面以rxJava调用为例</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Flowable.just(photos)</span><br><span class="line">    .observeOn(Schedulers.io())</span><br><span class="line">    .map(new Function&lt;List&lt;String&gt;, List&lt;File&gt;&gt;() &#123;</span><br><span class="line">      @Override public List&lt;File&gt; apply(@NonNull List&lt;String&gt; list) throws Exception &#123;</span><br><span class="line">        // 同步方法直接返回压缩后的文件</span><br><span class="line">        return Luban.with(MainActivity.this).load(list).get();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    .observeOn(AndroidSchedulers.mainThread())</span><br><span class="line">    .subscribe();</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;以上，均是它github上面说明都有的，我这里就是copy过来了而已。重点要说的是，他是怎么实现的，源码分析。</p>
<h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><h3 id="第一步：Luban-with"><a href="#第一步：Luban-with" class="headerlink" title="第一步：Luban.with()"></a>第一步：<strong><em>Luban.with()</em></strong></h3><p>点击去看到源码为：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public static Builder with(Context context) &#123;</span><br><span class="line">    return new Builder(context);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这里是一个静态的with方法，返回值是Builder，一般对设计模式比较熟悉的人，看到这里就应该懂了，他这里使用的是建造者模式。什么是建造者模式呢？建造者模式和工厂模式很相似，比工厂模式多了一个控制类，其实说白了，就是在创建对象的时候，减少初始化数据的代码，怎么理解呢？我们接着往下看。我们点到Builder里面看到如下代码：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">public static class Builder &#123;</span><br><span class="line">    private Context context;//上下文对象</span><br><span class="line">    private String mTargetDir;//压缩后图片存放位置</span><br><span class="line">    private List&lt;String&gt; mPaths;//多个文件的list</span><br><span class="line">    private int mLeastCompressSize = 100;//忽略100kb以下的图片，不压缩</span><br><span class="line">    private OnCompressListener mCompressListener;//回调方法</span><br><span class="line"></span><br><span class="line">    Builder(Context context) &#123;</span><br><span class="line">      this.context = context;</span><br><span class="line">      this.mPaths = new ArrayList&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    private Luban build() &#123;</span><br><span class="line">      return new Luban(this);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;我们看到了是一个静态的内部类Builder，我们这里看到了有5个变量，上面我们说道了，为了减少初始化数据的代码，就拿这个举例子说明，我如果有4个地方调用这个鲁班压缩，其中这4个地方，mTargetDir，mLeastCompressSize这2个变量的值都是一样的，其他3个不一样，按照我们以往的写法都得一个一个的赋值，要写4遍，那如果使用建造者模式了，这里就只用写一遍赋值，这2个变量。其他3个不一样，就得写多遍。当然，这是我个人对于建造者模式的理解。</p>
<p>&emsp;&emsp;我上面多粘贴了一个<strong><em>build()</em></strong>方法，为什么会多粘贴一个呢？就是为了更好的说明建造者模式，我们可以看到他这个方法，返回的是Luban对象，调用的是需要传Builder的构造方法，我们点进去看</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">private Luban(Builder builder) &#123;</span><br><span class="line">    this.mPaths = builder.mPaths;</span><br><span class="line">    this.mTargetDir = builder.mTargetDir;</span><br><span class="line">    this.mCompressListener = builder.mCompressListener;</span><br><span class="line">    this.mLeastCompressSize = builder.mLeastCompressSize;</span><br><span class="line">    mHandler = new Handler(Looper.getMainLooper(), this);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;他这里就是赋值，他这个值就是Builder里面默认的，我们不论在哪里调用这个方法，都不用去一个一个赋值，因为，他已经处理好了。</p>
<h3 id="第二步：load"><a href="#第二步：load" class="headerlink" title="第二步：load()"></a>第二步：<strong><em>load()</em></strong></h3><p>点击去看到源码为</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">public Builder load(File file) &#123;</span><br><span class="line">      this.mPaths.add(file.getAbsolutePath());</span><br><span class="line">      return this;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Builder load(String string) &#123;</span><br><span class="line">      this.mPaths.add(string);</span><br><span class="line">      return this;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Builder load(List&lt;String&gt; list) &#123;</span><br><span class="line">      this.mPaths.addAll(list);</span><br><span class="line">      return this;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这里，我们会看到三个重载方法，一个传文件，他会获取到文件的绝对路径存进去，实际上还是存的字符串，中间那个存的是字符串，最后面那个传String类型的list，它调用的addAll方法，最后还是存的String在mPaths里面。我们点击mPaths，他就是一个String类型的list，在Builder的构造方法里面初始化的。他就是存放你的图片路径的集合</p>
<h3 id="第三步：ignoreBy-和-setTargetDir"><a href="#第三步：ignoreBy-和-setTargetDir" class="headerlink" title="第三步：ignoreBy() 和 setTargetDir()"></a>第三步：<strong><em>ignoreBy()</em></strong> 和 <strong><em>setTargetDir()</em></strong></h3><p>点击去看到源码为</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * do not compress when the origin image file size less than one value</span><br><span class="line"> *</span><br><span class="line"> * @param size</span><br><span class="line"> *     the value of file size, unit KB, default 100K</span><br><span class="line"> */</span><br><span class="line">public Builder ignoreBy(int size) &#123;</span><br><span class="line">  this.mLeastCompressSize = size;</span><br><span class="line">  return this;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public Builder setTargetDir(String targetDir) &#123;</span><br><span class="line">  this.mTargetDir = targetDir;</span><br><span class="line">  return this;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这两个我为啥要放在一起讲呢？因为这两个没啥好说的，都是设置值，跟我们平时写的set方法的作用是一样的。没啥好说的</p>
<h3 id="第四步：setCompressListener-OnCompressListener-listener"><a href="#第四步：setCompressListener-OnCompressListener-listener" class="headerlink" title="第四步：setCompressListener(OnCompressListener listener)"></a>第四步：<strong><em>setCompressListener(OnCompressListener listener)</em></strong></h3><p>点击去看到源码为</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public Builder setCompressListener(OnCompressListener listener) &#123;</span><br><span class="line">      this.mCompressListener = listener;</span><br><span class="line">      return this;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这个就是我们平时写自定义view的时候，要写回调方法，是一样的道理，他这里就是压缩方法的回调</p>
<h3 id="第五步：launch"><a href="#第五步：launch" class="headerlink" title="第五步：launch()"></a>第五步：<strong><em>launch()</em></strong></h3><p>点击去看到源码为</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * begin compress image with asynchronous</span><br><span class="line"> */</span><br><span class="line">public void launch() &#123;</span><br><span class="line">  build().launch(context);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这里，我们看到他先调用了build(),我们前面讲了，他这个方法就是赋值，然后调用了launch(context)方法，我们点进去看：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">   * start asynchronous compress thread</span><br><span class="line">   */</span><br><span class="line">  @UiThread private void launch(final Context context) &#123;</span><br><span class="line">    if (mPaths == null || mPaths.size() == 0 &amp;&amp; mCompressListener != null) &#123;</span><br><span class="line">      mCompressListener.onError(new NullPointerException(&quot;image file cannot be null&quot;));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Iterator&lt;String&gt; iterator = mPaths.iterator();</span><br><span class="line">    while (iterator.hasNext()) &#123;</span><br><span class="line">      final String path = iterator.next();</span><br><span class="line">      if (Checker.isImage(path)) &#123;</span><br><span class="line">        AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() &#123;</span><br><span class="line">          @Override public void run() &#123;</span><br><span class="line">            try &#123;</span><br><span class="line">              mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_START));</span><br><span class="line"></span><br><span class="line">              File result = Checker.isNeedCompress(mLeastCompressSize, path) ?</span><br><span class="line">                  new Engine(path, getImageCacheFile(context, Checker.checkSuffix(path))).compress() :</span><br><span class="line">                  new File(path);</span><br><span class="line"></span><br><span class="line">              mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_SUCCESS, result));</span><br><span class="line">            &#125; catch (IOException e) &#123;</span><br><span class="line">              mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_ERROR, e));</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        Log.e(TAG, &quot;can not read the path : &quot; + path);</span><br><span class="line">      &#125;</span><br><span class="line">      iterator.remove();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这个方法就是最后，执行压缩的方法，前面都是初始化，我们可以看到，他这个方法是在主线程调用的，所以，我们不用考虑切换线程的问题，直接可以操作UI变化。我一步一步的讲：</p>
<ol>
<li>首先，他这个是用的迭代器，循环遍历，遍历一个就移除一个</li>
<li>然后就是通过handler发消息调用</li>
<li>具体压缩代码。最重要的就是第三点，我把第三点，提到下面讲</li>
</ol>
<p>接着上面的第三点，具体压缩</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">File result = Checker.isNeedCompress(mLeastCompressSize, path) ?</span><br><span class="line">                  new Engine(path, getImageCacheFile(context, Checker.checkSuffix(path))).compress() :</span><br><span class="line">                  new File(path);</span><br></pre></td></tr></table></figure></div>
<p>首先，他整体是一个三目运算符，我们点isNeedCompress()方法看一下</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">static boolean isNeedCompress(int leastCompressSize, String path) &#123;</span><br><span class="line">    if (leastCompressSize &gt; 0) &#123;</span><br><span class="line">      File source = new File(path);</span><br><span class="line">      if (!source.exists()) &#123;</span><br><span class="line">        return false;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      if (source.length() &lt;= (leastCompressSize &lt;&lt; 10)) &#123;</span><br><span class="line">        return false;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return true;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这个方法就是用来判断，你给定路径的图片大小和你规定的忽略文件大小比较，他这里先做了你给定的最小值判断，要大于0，不大于0就返回ture。然后做了文件是否存在的判断，如果文件不存在，就返回fals。最后，给定文件大小是不是小于等于最小值左移10位的值，小于就返回false。</p>
<p>然后，如果返回的是true，就去压缩，如果，返回的是false，就直接返回file文件。压缩的方法点进去：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Engine(String srcImg, File tagImg) throws IOException &#123;</span><br><span class="line">    if (Checker.isJPG(srcImg)) &#123;</span><br><span class="line">      this.srcExif = new ExifInterface(srcImg);</span><br><span class="line">    &#125;</span><br><span class="line">    this.tagImg = tagImg;</span><br><span class="line">    this.srcImg = srcImg;</span><br><span class="line"></span><br><span class="line">    BitmapFactory.Options options = new BitmapFactory.Options();</span><br><span class="line">    options.inJustDecodeBounds = true;</span><br><span class="line">    options.inSampleSize = 1;</span><br><span class="line"></span><br><span class="line">    BitmapFactory.decodeFile(srcImg, options);</span><br><span class="line">    this.srcWidth = options.outWidth;</span><br><span class="line">    this.srcHeight = options.outHeight;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这就又要说道另一个类了Engine类，它的类注释就是：用于操作，开始压缩，管理活动，缓存资源的类。他这里传原文件，也就是你需要压缩的图片，还有一个就是目标文件，也就是你压缩之后，要保存的文件。</p>
<p>&emsp;&emsp;我们先看第二个参数是什么怎么传的，有的人看不懂</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">   * Returns a mFile with a cache audio name in the private cache directory.</span><br><span class="line">   *</span><br><span class="line">   * @param context</span><br><span class="line">   *     A context.</span><br><span class="line">   */</span><br><span class="line">  private File getImageCacheFile(Context context, String suffix) &#123;</span><br><span class="line">    if (TextUtils.isEmpty(mTargetDir)) &#123;</span><br><span class="line">      mTargetDir = getImageCacheDir(context).getAbsolutePath();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    String cacheBuilder = mTargetDir + &quot;/&quot; +</span><br><span class="line">        System.currentTimeMillis() +</span><br><span class="line">        (int) (Math.random() * 1000) +</span><br><span class="line">        (TextUtils.isEmpty(suffix) ? &quot;.jpg&quot; : suffix);</span><br><span class="line"></span><br><span class="line">    return new File(cacheBuilder);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;他这里就是新建一个文件，设置路径，设置名称，然后返回文件</p>
<p>&emsp;&emsp;再掉回去看Engine的构造方法，我们这里获取到了源文件和目标文件，我们只用把压缩后的流存到目标文件就行了。我之前写过一篇关于图片压缩的博客。它这里的option就是设置压缩的参数，不懂的可以看一下我之前的博客，或者用google百度一下就知道了。具体压缩就是用的bitmap的工厂类，调用的decodeFile方法。没错就是这一句 <strong><em>BitmapFactory.decodeFile(srcImg, options);</em></strong></p>
<p>最后，辣么一切都准备就绪了，怎么样开始压缩呢？<strong><em>compress()</em></strong></p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">File compress() throws IOException &#123;</span><br><span class="line">    BitmapFactory.Options options = new BitmapFactory.Options();</span><br><span class="line">    options.inSampleSize = computeSize();</span><br><span class="line"></span><br><span class="line">    Bitmap tagBitmap = BitmapFactory.decodeFile(srcImg, options);</span><br><span class="line">    ByteArrayOutputStream stream = new ByteArrayOutputStream();</span><br><span class="line"></span><br><span class="line">    tagBitmap = rotatingImage(tagBitmap);</span><br><span class="line">    tagBitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream);</span><br><span class="line">    tagBitmap.recycle();</span><br><span class="line"></span><br><span class="line">    FileOutputStream fos = new FileOutputStream(tagImg);</span><br><span class="line">    fos.write(stream.toByteArray());</span><br><span class="line">    fos.flush();</span><br><span class="line">    fos.close();</span><br><span class="line">    stream.close();</span><br><span class="line"></span><br><span class="line">    return tagImg;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这里面就是常规的压缩，存储的逻辑了，最最重要的压缩算法呢？就是这里的<strong><em>computeSize()</em></strong>方法</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">private int computeSize() &#123;</span><br><span class="line">    srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;</span><br><span class="line">    srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;</span><br><span class="line"></span><br><span class="line">    int longSide = Math.max(srcWidth, srcHeight);</span><br><span class="line">    int shortSide = Math.min(srcWidth, srcHeight);</span><br><span class="line"></span><br><span class="line">    float scale = ((float) shortSide / longSide);</span><br><span class="line">    if (scale &lt;= 1 &amp;&amp; scale &gt; 0.5625) &#123;</span><br><span class="line">      if (longSide &lt; 1664) &#123;</span><br><span class="line">        return 1;</span><br><span class="line">      &#125; else if (longSide &gt;= 1664 &amp;&amp; longSide &lt; 4990) &#123;</span><br><span class="line">        return 2;</span><br><span class="line">      &#125; else if (longSide &gt; 4990 &amp;&amp; longSide &lt; 10240) &#123;</span><br><span class="line">        return 4;</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        return longSide / 1280 == 0 ? 1 : longSide / 1280;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; else if (scale &lt;= 0.5625 &amp;&amp; scale &gt; 0.5) &#123;</span><br><span class="line">      return longSide / 1280 == 0 ? 1 : longSide / 1280;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      return (int) Math.ceil(longSide / (1280.0 / scale));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">private Bitmap rotatingImage(Bitmap bitmap) &#123;</span><br><span class="line">    if (srcExif == null) return bitmap;</span><br><span class="line"></span><br><span class="line">    Matrix matrix = new Matrix();</span><br><span class="line">    int angle = 0;</span><br><span class="line">    int orientation = srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);</span><br><span class="line">    switch (orientation) &#123;</span><br><span class="line">      case ExifInterface.ORIENTATION_ROTATE_90:</span><br><span class="line">        angle = 90;</span><br><span class="line">        break;</span><br><span class="line">      case ExifInterface.ORIENTATION_ROTATE_180:</span><br><span class="line">        angle = 180;</span><br><span class="line">        break;</span><br><span class="line">      case ExifInterface.ORIENTATION_ROTATE_270:</span><br><span class="line">        angle = 270;</span><br><span class="line">        break;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    matrix.postRotate(angle);</span><br><span class="line"></span><br><span class="line">    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;你以为我会一步一步给你讲<a href="https://github.com/Curzibn/Luban/blob/master/DESCRIPTION.md" target="_blank" rel="noopener">Luban算法逻辑</a>吗？那是不可能的，我特么都不会，怎么给你讲。我直接把他github上算法逻辑的介绍拷贝过来了：</p>
<ol>
<li>判断图片比例值，是否处于以下区间内；</li>
</ol>
<ul>
<li>[1, 0.5625) 即图片处于 [1:1 ~ 9:16) 比例范围内</li>
<li>[0.5625, 0.5) 即图片处于 [9:16 ~ 1:2) 比例范围内</li>
<li>[0.5, 0) 即图片处于 [1:2 ~ 1:∞) 比例范围内</li>
</ul>
<ol>
<li>判断图片最长边是否过边界值；</li>
</ol>
<ul>
<li>[1, 0.5625) 边界值为：1664 <em> n（n=1）, 4990 </em> n（n=2）, 1280 * pow(2, n-1)（n≥3）</li>
<li>[0.5625, 0.5) 边界值为：1280 * pow(2, n-1)（n≥1）</li>
<li>[0.5, 0) 边界值为：1280 * pow(2, n-1)（n≥1）</li>
</ul>
<ol>
<li>计算压缩图片实际边长值，以第2步计算结果为准，超过某个边界值则：width / pow(2, n-1)，height/pow(2, n-1)</li>
<li>计算压缩图片的实际文件大小，以第2、3步结果为准，图片比例越大则文件越大。<br>size = (newW <em> newH) / (width </em> height) * m；</li>
</ol>
<ul>
<li>[1, 0.5625) 则 width &amp; height 对应 1664，4990，1280 * n（n≥3），m 对应 150，300，300；</li>
<li>[0.5625, 0.5) 则 width = 1440，height = 2560, m = 200；</li>
<li>[0.5, 0) 则 width = 1280，height = 1280 / scale，m = 500；注：scale为比例值</li>
</ul>
<ol>
<li>判断第4步的size是否过小</li>
</ol>
<ul>
<li>[1, 0.5625) 则最小 size 对应 60，60，100</li>
<li>[0.5625, 0.5) 则最小 size 都为 100</li>
<li>[0.5, 0) 则最小 size 都为 100</li>
</ul>
<ol>
<li>将前面求到的值压缩图片 width, height, size 传入压缩流程，压缩图片直到满足以上数值</li>
</ol>

      
    </div>
    
      <footer class="article-footer">
        完
      </footer>
    
  </div>
  
    
<nav id="article-nav">
  <div class="article-nav-block">
    
      <a href="/2018/03/05/Android拍照功能——TakePhoto/" id="article-nav-newer" class="article-nav-link-wrap">
        <strong class="article-nav-caption"></strong>
        <div class="article-nav-title">
          
            Android拍照功能——TakePhoto
          
        </div>
      </a>
    
  </div>
  <div class="article-nav-block">
    
      <a href="/2018/03/01/从零开始搭建一个主流项目框架（八）—私有目录被限制访问-Android7.0/" id="article-nav-older" class="article-nav-link-wrap">
        <div class="article-nav-title">从零开始搭建一个主流项目框架（八）—— 私有目录被限制访问(Android 7.0)</div>
        <strong class="article-nav-caption"></strong>
      </a>
    
  </div>
</nav>

    
  
  
</article>
</section>
        <aside id="sidebar">
  
    <div class="widget-box">
  <div class="avatar-box avatar-item">
    <img class="avatar" src="/uploads/artistic_image/head.jpg" title="头像来自网络"></img>
    <h3 class="avatar-name">
      
        海晨忆
      
    </h3>
    <p class="avatar-slogan">
      先谋生，再谋爱。人间值得，未来可期。
    </p>
  </div>
</div>


  
    
  <div class="widget-box">
    <h3 class="widget-title">分类</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Android-Socket/">Android -Socket</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-加密算法/">Android -加密算法</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-常用功能/">Android -常用功能</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-框架/">Android -框架</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-源码解析/">Android -源码解析</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-自定义view/">Android -自定义view</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-设计模式/">Android -设计模式</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/JAVA-并发/">JAVA -并发</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Java-SpringBoot/">Java -SpringBoot</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Tomcat/">Tomcat</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/kotlin/">kotlin</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-JDBC/">数据库 -JDBC</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-MySQL/">数据库 -MySQL</a></li></ul>
    </div>
  </div>


  
    

  
    
  
    
  <div class="widget-box">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/07/">July 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/11/">November 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/10/">October 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/09/">September 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/08/">August 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/07/">July 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/06/">June 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/10/">October 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/05/">May 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/03/">March 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/02/">February 2018</a></li></ul>
    </div>
  </div>

  
    
  <div class="widget-box">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2021/07/27/JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁/">JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁</a>
          </li>
        
          <li>
            <a href="/2021/07/19/JAVA—线程同步器AQS/">JAVA—线程同步器AQS</a>
          </li>
        
          <li>
            <a href="/2021/07/07/Android模拟点击/">Android模拟点击</a>
          </li>
        
          <li>
            <a href="/2019/11/27/SpringBoot系列-消息-RabbitMQ-（二十）/">SpringBoot系列-消息(RabbitMQ)（二十）</a>
          </li>
        
          <li>
            <a href="/2019/11/13/SpringBoot系列-Docker（十九）/">SpringBoot系列-Docker（十九）</a>
          </li>
        
      </ul>
    </div>
  </div>

  
      <div class="widget-box">
    <h3 class="widget-title">友情链接</h3>
    <div class="widget">
      
        <a class="hrf" style="display: block;" href="https://github.com/haichenyi" title target='_blank'
        >Github</a>
      
        <a class="hrf" style="display: block;" href="https://www.jianshu.com/u/6077ee440c37" title target='_blank'
        >简书</a>
      
        <a class="hrf" style="display: block;" href="https://blog.csdn.net/qq_27634797" title target='_blank'
        >CSDN</a>
      
    </div>
  </div>

  
</aside>
      </div>
      <footer id="footer">
  <div class="foot-box footers global-width">
    &copy;2017-2021 海晨忆 &nbsp;&nbsp;
    <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span id="busuanzi_container_site_pv">阁下是第<span id="busuanzi_value_site_pv"></span>个访客</span>
  </div>
</footer>
      <script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
if (!window.jQuery) {
var script = document.createElement('script');
script.src = "/js/jquery-2.0.3.min.js";
document.body.write(script);
}
</script>

  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>



    </div>
    <nav id="mobile-nav" class="mobile-nav-box">
  <div class="mobile-nav-img mobile-nav-top"></div>
  
    <a href="/" class="mobile-nav-link">首页</a>
  
    <a href="/archives" class="mobile-nav-link">归档</a>
  
    <a href="/about" class="mobile-nav-link">简历</a>
  
  <div class="mobile-nav-img  mobile-nav-bottom"></div>
</nav>    
  </div>
</body>
</html>